summaryrefslogtreecommitdiff
path: root/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:56 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-20 11:37:56 +0000
commit994defd6446ce20c4b4e0d6cc91688b0e64230a4 (patch)
tree750bc8a9c7a16908a8e2f01761ab087a72520cff /app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
parentaa86729f9a2ab95346a2851e3837de1c367aae17 (diff)
(최겸) 기술영업 파트너 페이지 작업사항
Diffstat (limited to 'app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx')
-rw-r--r--app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx179
1 files changed, 169 insertions, 10 deletions
diff --git a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
index 40be6773..b9c957f0 100644
--- a/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
+++ b/app/[lng]/partners/(partners)/techsales/rfq-offshore-top/page.tsx
@@ -1,17 +1,176 @@
+import * as React from "react";
+import Link from "next/link";
+import { Metadata } from "next";
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { LogIn } from "lucide-react";
import { Shell } from "@/components/shell";
+import {
+ TECH_SALES_QUOTATION_STATUSES,
+ TECH_SALES_QUOTATION_STATUS_CONFIG
+} from "@/db/schema";
+
+import { getQuotationStatusCounts } from "@/lib/techsales-rfq/service";
+import { VendorQuotationsTable } from "@/lib/techsales-rfq/vendor-response/table/vendor-quotations-table";
+
+export const metadata: Metadata = {
+ title: "기술영업 해양TOP 견적서 관리",
+ description: "기술영업 해양TOP RFQ 견적서를 관리합니다.",
+};
+
+export default async function VendorQuotationsTopPage() {
+ // 세션 확인
+ const session = await getServerSession(authOptions);
+
+ if (!session?.user) {
+ return (
+ <Shell>
+ <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4">
+ <div className="text-center">
+ <h2 className="text-2xl font-bold tracking-tight">로그인이 필요합니다</h2>
+ <p className="text-muted-foreground">
+ 견적서를 확인하려면 로그인해주세요.
+ </p>
+ </div>
+ <Button asChild>
+ <Link href="/api/auth/signin">
+ <LogIn className="mr-2 h-4 w-4" />
+ 로그인
+ </Link>
+ </Button>
+ </div>
+ </Shell>
+ );
+ }
+
+ // 벤더 ID 확인 (사용자의 회사 ID가 벤더 ID)
+ const vendorId = session.user.techCompanyId;
+ if (!vendorId) {
+ return (
+ <Shell>
+ <div className="flex min-h-[400px] flex-col items-center justify-center space-y-4">
+ <div className="text-center">
+ <h2 className="text-2xl font-bold tracking-tight">기술영업 벤더 정보가 없습니다</h2>
+ <p className="text-muted-foreground">
+ 기술영업 벤더 정보가 없습니다. 관리자에게 문의하세요.
+ </p>
+ </div>
+ </div>
+ </Shell>
+ );
+ }
+
+ // 견적서 상태별 개수 조회
+ const statusCountsPromise = getQuotationStatusCounts(vendorId.toString(), "TOP");
-export default function TechSalesRfqShipPage() {
return (
- <Shell className="gap-6">
- <div>
- <h1 className="text-2xl font-bold">기술영업 - 해양 Hull/Top RFQ</h1>
- <p className="text-muted-foreground">
- 벤더가 해양 Hull/Top RFQ 목록을 확인하고 관리합니다.
- </p>
- <p className="text-muted-foreground">
- 기술영업 해양 Hull/Top 은 업무 요구사항이 동일하다면 통합으로 개발될 수 있습니다.
- </p>
+ <Shell variant="fullscreen" className="h-full">
+ {/* 고정 헤더 영역 */}
+ <div className="flex-shrink-0">
+ <div className="flex-shrink-0 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
+ <div>
+ <h1 className="text-3xl font-bold tracking-tight">기술영업 해양TOP 견적서</h1>
+ <p className="text-muted-foreground">
+ 할당받은 해양TOP RFQ에 대한 견적서를 작성하고 관리합니다.
+ </p>
+ </div>
+ </div>
+
+ {/* 상태별 개수 카드 */}
+ <div className="flex-shrink-0">
+ <React.Suspense
+ fallback={
+ <div className="w-full overflow-x-auto">
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
+ {Array.from({ length: 5 }).map((_, i) => (
+ <Card key={i} className="min-w-[160px]">
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium truncate">로딩중...</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">-</div>
+ </CardContent>
+ </Card>
+ ))}
+ </div>
+ </div>
+ }
+ >
+ <StatusCards statusCountsPromise={statusCountsPromise} />
+ </React.Suspense>
+ </div>
+
+ {/* 견적서 테이블 */}
+ <div className="flex-1 min-h-0 overflow-hidden">
+ <div className="h-full overflow-auto">
+ <VendorQuotationsTable vendorId={vendorId.toString()} rfqType="TOP" />
+ </div>
+ </div>
</div>
</Shell>
);
+}
+
+// 상태별 개수 카드 컴포넌트
+async function StatusCards({
+ statusCountsPromise,
+}: {
+ statusCountsPromise: Promise<{
+ data: { status: string; count: number }[] | null;
+ error: string | null;
+ }>;
+}) {
+ const { data: statusCounts, error } = await statusCountsPromise;
+
+ if (error || !statusCounts) {
+ return (
+ <div className="w-full overflow-x-auto">
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 min-w-fit">
+ <Card className="min-w-[160px]">
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium truncate">오류</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold text-red-600">-</div>
+ <p className="text-xs text-muted-foreground truncate">
+ 데이터를 불러올 수 없습니다
+ </p>
+ </CardContent>
+ </Card>
+ </div>
+ </div>
+ );
+ }
+
+ // 중앙화된 상태 설정 사용
+ const statusEntries = Object.entries(TECH_SALES_QUOTATION_STATUSES).map(([, statusValue]) => ({
+ key: statusValue,
+ ...TECH_SALES_QUOTATION_STATUS_CONFIG[statusValue]
+ }));
+
+ console.log(statusCounts, "statusCounts")
+
+ return (
+ <div className="w-full overflow-x-auto">
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 min-w-fit">
+ {statusEntries.map((status) => (
+ <Card key={status.key} className="min-w-[160px]">
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+ <CardTitle className="text-sm font-medium truncate">{status.label}</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className={`text-2xl font-bold ${status.color}`}>
+ {statusCounts.find(item => item.status === status.key)?.count || 0}
+ </div>
+ <p className="text-xs text-muted-foreground truncate">
+ {status.description}
+ </p>
+ </CardContent>
+ </Card>
+ ))}
+ </div>
+ </div>
+ );
} \ No newline at end of file